package org.jeecg.modules.flowable.service.impl;

import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.RequiredArgsConstructor;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.EndEvent;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.UserTask;
import org.flowable.editor.language.json.converter.BaseBpmnJsonConverter;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.image.ProcessDiagramGenerator;
import org.flowable.ui.common.util.XmlUtil;
import org.flowable.ui.modeler.domain.AbstractModel;
import org.flowable.ui.modeler.domain.Model;
import org.flowable.ui.modeler.model.ModelKeyRepresentation;
import org.flowable.ui.modeler.model.ModelRepresentation;
import org.flowable.ui.modeler.serviceapi.ModelService;
import org.flowable.validation.ProcessValidator;
import org.flowable.validation.ProcessValidatorFactory;
import org.flowable.validation.ValidationError;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.modules.flowable.converter.CustomBpmnJsonConverter;
import org.jeecg.modules.flowable.dto.*;
import org.jeecg.modules.flowable.entity.FlowInstance;
import org.jeecg.modules.flowable.entity.FlowModel;
import org.jeecg.modules.flowable.entity.FlowModelParticipant;
import org.jeecg.modules.flowable.enums.ModelUseStatusEnum;
import org.jeecg.modules.flowable.enums.ParticipantTargetTypeEnum;
import org.jeecg.modules.flowable.enums.ParticipantUseScenariosEnum;
import org.jeecg.modules.flowable.mapper.FlowInstanceMapper;
import org.jeecg.modules.flowable.mapper.FlowModelMapper;
import org.jeecg.modules.flowable.query.FlowModelQuery;
import org.jeecg.modules.flowable.service.FlowModelParticipantService;
import org.jeecg.modules.flowable.service.FlowModelService;
import org.jeecg.modules.flowable.service.FlowOperatorService;
import org.jeecg.modules.flowable.utils.BeanUtils;
import org.jeecg.modules.flowable.utils.flow.FlowableModelUtil;
import org.jeecg.modules.flowable.vo.FlowModelVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class FlowModelServiceImpl extends ServiceImpl<FlowModelMapper, FlowModel> implements FlowModelService {

    private static final String BPMN_EXTENSION = ".bpmn";


    final ModelService modelService;
    final ProcessEngine processEngine;
    final RuntimeService runtimeService;
    final HistoryService historyService;
    final BpmnXMLConverter bpmnXmlConverter;
    final RepositoryService repositoryService;
    final FlowInstanceMapper flowInstanceMapper;
    final CustomBpmnJsonConverter bpmnJsonConverter;
    final ProcessValidatorFactory processValidatorFactory;
    final FlowModelParticipantService flowModelParticipantService;

    @Autowired
    FlowOperatorService flowOperatorService;

    @Resource
    private FlowModelMapper flowModelMapper;

    @Override
    @Transactional
    public boolean save(FlowModel entity) {
        if (isExistsKey(entity.getModelKey())) {
            throw new JeecgBootException("模型key已存在！");
        }
        Model model = this.createModel(entity);
        entity.setId(model.getId());
        entity.setUseStatus(ModelUseStatusEnum.DRAFT.getCode());
        return super.save(entity);
    }

    @Override
    @Transactional
    public Result<Void> publish(String modelId) {
        FlowModel flowModel = this.getById(modelId);
        if (Objects.isNull(flowModel)) {
            return Result.error("没有找到对应的模型，请确认!");
        }
        if (ModelUseStatusEnum.DRAFT.getCode().equals(flowModel.getUseStatus())) {
            return Result.error("流程尚未设置任务节点，请先进行流程设计后再发布！");
        }
        if (ModelUseStatusEnum.USING.getCode().equals(flowModel.getUseStatus())) {
            return Result.error("该流程模型已发布！");
        }

        // 部署流程
        Model model = this.modelService.getModel(flowModel.getId());
        BpmnModel bpmnModel = this.modelService.getBpmnModel(model);
        Deployment deploy = this.repositoryService.createDeployment()
                .name(model.getName())
                .key(model.getKey())
                .category(flowModel.getCategoryName())
                .tenantId(model.getTenantId())
                .addBpmnModel(model.getKey() + BPMN_EXTENSION, bpmnModel)
                .deploy();

        // 查询部署流程Id
        ProcessDefinition processDefinition = this.repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploy.getId())
                .singleResult();
        // 更新状态信息
        flowModel.setProcessDefinitionId(processDefinition.getId());
        flowModel.setUseStatus(ModelUseStatusEnum.USING.getCode());
        this.updateById(flowModel);
        // 获取流程节点配置信息

        Result<Void> loadSettingResult = this.modelSetting2(flowModel, bpmnModel);
        if (!loadSettingResult.isSuccess()) {
            throw new JeecgBootException(loadSettingResult.getMessage());
        }
        return Result.OK();
    }

    @Override
    public Result<Void> importBpmnModel(BpmnModelDTO dto) {
        String modelId = dto.getId();
        String modelXml = dto.getXml();
        FlowModel flowModel = this.getById(modelId);
        if (Objects.isNull(flowModel)) {
            return Result.error("流程设计错误，请退出后重试！");
        }
        Model model = this.modelService.getModel(modelId);
        // 读取xml内容信息
        ByteArrayInputStream modelStream = new ByteArrayInputStream(modelXml.getBytes());
        // xml处理
        XMLInputFactory xif = XmlUtil.createSafeXmlInputFactory();
        InputStreamReader xmlIn = new InputStreamReader(modelStream, StandardCharsets.UTF_8);
        XMLStreamReader xtr;
        BpmnModel bpmnModel;
        try {
            xtr = xif.createXMLStreamReader(xmlIn);
            // 将xml文件流转换成流程模型
            bpmnModel = this.bpmnXmlConverter.convertToBpmnModel(xtr);
        } catch (XMLStreamException e) {
            throw new JeecgBootException("create process bpmn xml error");
        }
        bpmnModel.getMainProcess().setId(model.getKey());
        bpmnModel.setTargetNamespace(BaseBpmnJsonConverter.NAMESPACE);

        if (CollectionUtils.isEmpty(bpmnModel.getLocationMap())) {
            throw new JeecgBootException("No required BPMN DI information found in definition ");
        }
        ProcessValidator processValidator = this.processValidatorFactory.createDefaultProcessValidator();
        List<ValidationError> validationErrors = processValidator.validate(bpmnModel);
        if (!CollectionUtils.isEmpty(validationErrors)) {
            StringBuffer message = new StringBuffer();
            validationErrors.forEach(validationError -> message.append(validationErrors));
            return Result.error(message.toString());
        }

        // 更新保存流程模型
        ObjectNode modelNode = this.bpmnJsonConverter.convertToJson(bpmnModel);

        modelNode.put("process_id", model.getKey());
        this.modelService.saveModel(modelId, model.getName(), model.getKey(), model.getDescription(), modelNode.toString(), false, null, null);

        flowModel.setUseStatus(ModelUseStatusEnum.WAIT_RELEASED.getCode());
        this.updateById(flowModel);
        return Result.OK();
    }

    @Override
    public Result<FlowModelVO> loadBpmnXmlByModelId(String modelId) {
        FlowModel flowModel = this.getById(modelId);
        if (Objects.isNull(flowModel)) {
            return Result.error("查询模型不存在！");
        }
        Model model = this.modelService.getModel(modelId);
        byte[] bpmnXml = modelService.getBpmnXML(model);
        String streamStr = new String(bpmnXml, StandardCharsets.UTF_8);
        FlowModelVO vo = BeanUtils.convertTo(flowModel, FlowModelVO::new);
        vo.setId(model.getId());
        vo.setName(model.getName());
        vo.setKey(model.getKey());
        vo.setXml(streamStr);
        vo.setCategoryId(flowModel.getCategoryId());
        vo.setCategoryName(flowModel.getCategoryName());

        return Result.OK(vo);
    }

    @Override
    public Result<FlowModelVO> loadBpmnXmlByProcessDefinitionId(String processDefinitionId) {
        LambdaQueryWrapper<FlowModel> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(FlowModel::getProcessDefinitionId, processDefinitionId);
        List<FlowModel> modelList = this.baseMapper.selectList(wrapper);

        if (CollectionUtils.isEmpty(modelList)) {
            return Result.error("查询模型不存在！");
        }
        FlowModel flowModel = modelList.get(0);

        Model model = this.modelService.getModel(flowModel.getId());
        byte[] bpmnXml = modelService.getBpmnXML(model);
        String streamStr = new String(bpmnXml, StandardCharsets.UTF_8);
        FlowModelVO vo = BeanUtils.convertTo(flowModel, FlowModelVO::new);
        vo.setId(model.getId());
        vo.setName(model.getName());
        vo.setKey(model.getKey());
        vo.setXml(streamStr);
        vo.setCategoryId(flowModel.getCategoryId());
        vo.setCategoryName(flowModel.getCategoryName());
        return Result.OK(vo);
    }

    @Override
    public List<EndEvent> findEndFlowElement(String processDefinitionId) {
        BpmnModel bpmnModel = this.repositoryService.getBpmnModel(processDefinitionId);
        if (bpmnModel == null) {
            return Collections.emptyList();
        }
        Process process = bpmnModel.getMainProcess();
        return process.findFlowElementsOfType(EndEvent.class);
    }

    @Override
    public FlowModel getByBusinessKey(String businessKey) {
        if (ObjectUtils.isEmpty(businessKey)) {
            return null;
        }
        LambdaQueryWrapper<FlowModel> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(FlowModel::getBusinessKey, businessKey);
        wrapper.last("limit 1");
        return this.getOne(wrapper);
    }

    private Model createModel(FlowModel entity) {
        ModelRepresentation representation = new ModelRepresentation();
        representation.setModelType(AbstractModel.MODEL_TYPE_BPMN);
        representation.setKey(entity.getModelKey());
        representation.setName(entity.getModelName());
        representation.setLastUpdated(new Date());
        representation.setKey(representation.getKey().replaceAll(" ", ""));
        ModelKeyRepresentation modelKeyInfo = this.modelService.validateModelKey(null, representation.getModelType(), representation.getKey());
        if (modelKeyInfo.isKeyAlreadyExists()) {
            throw new JeecgBootException("Model Key is Exists or Version Error");
        }
        String json = this.modelService.createModelJson(representation);
        return this.modelService.createModel(representation, json, null);
    }

    public Result<Void> modelSetting2(FlowModel flowModel, BpmnModel bpmnModel) {
        // 获取主流程所有用户节点
        List<UserTask> userTaskList = bpmnModel.getMainProcess().findFlowElementsOfType(UserTask.class);
        if (CollectionUtils.isEmpty(userTaskList)) {
            return Result.OK();
        }

        String modelId = flowModel.getId();
        String modelKey = flowModel.getModelKey();
        String modelName = flowModel.getModelName();
        String processDefinitionId = flowModel.getProcessDefinitionId();

        // 组装参与人员信息
        List<FlowModelParticipant> modelParticipantList = new ArrayList<>();

        userTaskList.forEach(e -> {
            String ActivityId = (e.getId());
            String ActivityName = (e.getName());

            String assignee = e.getAssignee();
            if (assignee != null && assignee != "") {

//                USER(1, "user", "用户"),
//                ROLE(2, "role", "角色"),
//                DEPT(3, "dept", "部门");

                FlowUserDTO userDTO = flowOperatorService.getUserByUsername(assignee);
                if (userDTO != null) {
                    FlowModelParticipant participant = new FlowModelParticipant();
                    participant.setId(IdUtil.fastSimpleUUID());
                    participant.setTargetId(userDTO.getId());
                    participant.setTargetName(assignee);
                    participant.setTargetType(ParticipantTargetTypeEnum.USER.getCode());
                    participant.setModelId(modelId);
                    participant.setModelKey(modelKey);
                    participant.setModelName(modelName);
                    participant.setProcessDefinitionId(processDefinitionId);
                    participant.setActivityId(ActivityId);
                    participant.setUseScenarios(ParticipantUseScenariosEnum.APPROVE.getCode());
                    modelParticipantList.add(participant);
                }
            }

            List<String> candidateUsers = e.getCandidateUsers();
            if (!CollectionUtils.isEmpty(candidateUsers)) {
                candidateUsers.forEach(candidateUser -> {
                    FlowUserDTO userDTO = flowOperatorService.getUserByUsername(candidateUser);

                    FlowModelParticipant participant = new FlowModelParticipant();
                    participant.setId(IdUtil.fastSimpleUUID());
                    participant.setTargetId(userDTO.getId());
                    participant.setTargetName(candidateUser);
                    participant.setTargetType(ParticipantTargetTypeEnum.USER.getCode());
                    participant.setModelId(modelId);
                    participant.setModelKey(modelKey);
                    participant.setModelName(modelName);
                    participant.setProcessDefinitionId(processDefinitionId);
                    participant.setActivityId(ActivityId);
                    participant.setUseScenarios(ParticipantUseScenariosEnum.APPROVE.getCode());
                    modelParticipantList.add(participant);
                });
            }

            List<String> candidateGroups = e.getCandidateGroups();
            if (!CollectionUtils.isEmpty(candidateGroups)) {
                candidateGroups.forEach(candidateGroup -> {
                    FlowRoleDTO roleDTO = flowOperatorService.getRoleById(candidateGroup);

                    FlowModelParticipant participant = new FlowModelParticipant();
                    participant.setId(IdUtil.fastSimpleUUID());
                    participant.setTargetId(roleDTO.getId());
                    participant.setTargetName(roleDTO.getRoleName());
                    participant.setTargetType(ParticipantTargetTypeEnum.ROLE.getCode());
                    participant.setModelId(modelId);
                    participant.setModelKey(modelKey);
                    participant.setModelName(modelName);
                    participant.setProcessDefinitionId(processDefinitionId);
                    participant.setActivityId(ActivityId);
                    participant.setUseScenarios(ParticipantUseScenariosEnum.APPROVE.getCode());
                    modelParticipantList.add(participant);
                });
            }
        });

        this.flowModelParticipantService.saveBatch(modelParticipantList);
        return Result.OK();
    }

    public Result<Void> modelSetting(FlowModel flowModel, BpmnModel bpmnModel) {
        List<FlowTaskSettingDTO> dtoList = FlowableModelUtil.taskExpandInfo(bpmnModel);
        if (CollectionUtils.isEmpty(dtoList)) {
            return Result.OK();
        }
        String modelId = flowModel.getId();
        String modelKey = flowModel.getModelKey();
        String modelName = flowModel.getModelName();
        String processDefinitionId = flowModel.getProcessDefinitionId();

        // 组装参与人员信息
        List<FlowModelParticipant> modelParticipantList = new ArrayList<>();
        dtoList.forEach(e -> {
            List<FlowParticipantDTO> approvalParticipantList = e.getApprovalParticipantList();
            if (CollectionUtils.isEmpty(approvalParticipantList)) {
                return;
            }
            approvalParticipantList.forEach(approvalParticipant -> {
                FlowModelParticipant participant = new FlowModelParticipant();
                participant.setId(IdUtil.fastSimpleUUID());
                participant.setTargetId(approvalParticipant.getId().toString());
                participant.setTargetName(approvalParticipant.getName());
                participant.setTargetType(approvalParticipant.getType());
                participant.setModelId(modelId);
                participant.setModelKey(modelKey);
                participant.setModelName(modelName);
                participant.setProcessDefinitionId(processDefinitionId);
                participant.setActivityId(e.getActivityId());
                participant.setUseScenarios(ParticipantUseScenariosEnum.APPROVE.getCode());
                modelParticipantList.add(participant);
            });
        });
        this.flowModelParticipantService.saveBatch(modelParticipantList);
        return Result.OK();
    }

    public boolean isExistsKey(String key) {
        LambdaQueryWrapper<FlowModel> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(FlowModel::getModelKey, key);
        return super.count(wrapper) > 0;
    }

    @Override
    public void updateState(Integer state, String id) {

        FlowModel flowModel = this.getById(id);
        if (Objects.isNull(flowModel)) {
            return;
        }

        //flowModel.setUseStatus(ModelUseStatusEnum.USING.getCode());
        flowModel.setUseStatus(state);
        this.updateById(flowModel);

    }


    @Override
    public Page<FlowModelVO> getList(FlowModelQuery query) {
        return (this.baseMapper.getList(new Page<>(query.getPageIndex(), query.getPageSize()), query));
    }

    @Override
    public InputStream genProcessDiagram(String processDefinitionId) {
        // 获取流程图
        BpmnModel bpmnModel = this.repositoryService.getBpmnModel(processDefinitionId);
        ProcessEngineConfiguration config = this.processEngine.getProcessEngineConfiguration();
        ProcessDiagramGenerator diagramGenerator = config.getProcessDiagramGenerator();
        return diagramGenerator.generateDiagram(bpmnModel, "bmp", Collections.emptyList(), Collections.emptyList(),
                "宋体", "宋体", "宋体", config.getClassLoader(), 1.0, true);
    }

    @Override
    public InputStream genProcessDiagramSchedule(String processInstanceId) {
        // 根据流程实例ID获得当前处于活动状态的ActivityId合集
        FlowInstance flowInstance = this.flowInstanceMapper.selectById(processInstanceId);
        if (Objects.isNull(flowInstance)) {
            throw new RuntimeException("该流程实例不存在！");
        }
        String processDefinitionId = flowInstance.getProcessDefinitionId();
        // 获得历史活动的节点
        List<HistoricActivityInstance> historicActivityList = this.historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();
        List<String> highLightedActivitiList = historicActivityList.stream().map(HistoricActivityInstance::getActivityId).collect(Collectors.toList());
        // 获取流程图
        BpmnModel bpmnModel = this.repositoryService.getBpmnModel(processDefinitionId);
        ProcessEngineConfiguration config = this.processEngine.getProcessEngineConfiguration();

        ProcessDiagramGenerator diagramGenerator = config.getProcessDiagramGenerator();
        return diagramGenerator.generateDiagram(bpmnModel, "bmp", highLightedActivitiList, Collections.emptyList(),
                "宋体", "宋体", "宋体", config.getClassLoader(), 1.0, true);
    }

    @Override
    public FlowModel searchByBussinessKey(String BussinessKey) {
        LambdaQueryWrapper<FlowModel> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(FlowModel::getBusinessKey, BussinessKey);
        return flowModelMapper.selectOne(queryWrapper);
    }

}
